package org.smallframework.springboot;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.smallframework.spring.DaoMapperClassPathScanner;
import org.smallframework.spring.DaoMapperFactoryBean;
import org.smallframework.spring.DaoTemplate;
import org.smallframework.spring.OrmConfigFactoryBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.StringUtils;
import sf.common.exception.SmallOrmException;
import sf.database.OrmConfig;
import sf.database.dao.DBClient;
import sf.database.mapper.annotation.OrmMapper;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.util.List;

@Configuration
@ConditionalOnClass({DBClient.class})
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(SmallOrmProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class SmallOrmAutoConfiguration {
    public static final String DBCLIENT_NAME = "daoTemplate";

    private static final Logger logger = LoggerFactory.getLogger(SmallOrmAutoConfiguration.class);

    @Autowired
    private SmallOrmProperties properties;

    @Bean
    @ConditionalOnMissingBean
    public OrmConfig ormConfig(DataSource dataSource) {
        DaoTemplate dt = new DaoTemplate(dataSource);
        OrmConfigFactoryBean config = new OrmConfigFactoryBean();
        config.setDbClient(dt);
        config.setBeanValueType(properties.getBeanValueType());
        if (!StringUtils.hasText(properties.getPackages())){
            throw new SmallOrmException("请设置需要扫描的实体类包名");
        }
        config.setPackagesToScan(properties.getPackages().split(","));
        config.setUseTail(properties.isUseTail());
        config.setAllowDropColumn(properties.isAllowDropColumn());
        config.setCreateTable(properties.isCreateTable());
        config.setShowSql(properties.isShowSql());
        config.setShowCallChain(properties.isShowCallChain());
        config.setUseSystemPrint(properties.isUseSystemPrint());
        config.setSqlTemplatePath(properties.getSqlTemplatePath());
        config.setSqlTemplateDebug(properties.isSqlTemplateDebug());
        config.setBatchSize(properties.getBatchSize());
        config.setOpenStreamIterator(properties.isOpenStreamIterator());
        config.setSqlTemplateType(properties.getSqlTemplateType());
        config.setCheckEnhancement(properties.isCheckEnhancement());
        return config;
    }

    @Bean(name = DBCLIENT_NAME)
    @ConditionalOnMissingBean
    public DaoTemplate daoTemplate(OrmConfig config) {
        return (DaoTemplate) config.getDbClient();
    }

    /**
     * 配置mapper扫描
     */
    public static class AutoConfiguredMapperScannerRegistrar
            implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {

        private BeanFactory beanFactory;

        private ResourceLoader resourceLoader;

        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

            logger.debug("Searching for mappers annotated with @Mapper");

            DaoMapperClassPathScanner scanner = new DaoMapperClassPathScanner(registry);

            try {
                if (this.resourceLoader != null) {
                    scanner.setResourceLoader(this.resourceLoader);
                }

                List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
                if (logger.isDebugEnabled()) {
                    for (String pkg : packages) {
                        logger.debug("Using auto-configuration base package '{}'", pkg);
                    }
                }
                scanner.setDbClient(DBCLIENT_NAME);
                scanner.setAnnotationClass(OrmMapper.class);
                scanner.registerFilters();
                scanner.doScan(StringUtils.toStringArray(packages));
            } catch (IllegalStateException ex) {
                logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);
            }
        }

        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            this.beanFactory = beanFactory;
        }

        @Override
        public void setResourceLoader(ResourceLoader resourceLoader) {
            this.resourceLoader = resourceLoader;
        }
    }

    @Configuration
    @Import({AutoConfiguredMapperScannerRegistrar.class})
    @ConditionalOnMissingBean(DaoMapperFactoryBean.class)
    public static class MapperScannerRegistrarNotFoundConfiguration {

        @PostConstruct
        public void afterPropertiesSet() {
            logger.debug("No {} found.", DaoMapperFactoryBean.class.getName());
        }
    }
}
